Fork me on GitHub

Redis - 学习

注意:所有文章除特别说明外,转载请注明出处.

Redis - 面试总结

[TOC]

为什么要用redis而不用map做缓存?

Java实现的Map是本地缓存,如果有多台实例(机器)的话,每个实例都需要各自保存一份缓存,缓存不具有一致性

Redis实现的是分布式缓存,如果有多台实例(机器)的话,每个实例都共享一份缓存,缓存具有一致性。

Java实现的Map不是专业做缓存的,JVM内存太大容易挂掉。一般用做于容器来存储临时数据,缓存的数据随着JVM销毁而结束。Map所存储的数据结构,缓存过期机制等等是需要程序员自己手写的。

Redis是专业做缓存的,可以用几十个G内存来做缓存。Redis一般用作于缓存,可以将缓存数据保存在硬盘中,Redis重启了后可以将其恢复。原生提供丰富的数据结构、缓存过期机制等简单好用的功能。

为什么要用缓存?

如果网站出现了性能问题(访问时间慢),按经验来说,一般是由于数据库撑不住了。因为一般数据库的读写都是要经过磁盘的,而磁盘的速度可以说是相当慢的(相对内存来说)。

如何解决缓存雪崩?

对于==“对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。”==这种情况,非常好解决:

解决方法:在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。

对于“Redis挂掉了,请求全部走数据库”这种情况,我们可以有以下的思路:

事发前:实现Redis的高可用(主从架构+Sentinel 或者Redis Cluster),尽量避免Redis挂掉这种情况发生。

事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)

事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。

如何解决缓存穿透?

缓存穿透,表示每次请求的id都是数据库中不存在的数,这就会导致缓存没作用,请求全部查询数据库,但是数据库中没有这个值,所以每次返回null值回来。

缓存穿透是指查询一个一定不存在的数据。由于缓存不命中,并且出于容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。

解决:

1. 由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层。

2. 当我们从数据库找不到的时候,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了。

    提示:这种情况我们一般会将空对象设置一个较短的过期时间。

数据库与缓存双写一致?

读操作,如果从数据库查询不到数据则不写入缓存。一般的读操作有一个固定套路:

1. 如果我们的数据在缓存里边有,那么就直接取缓存的。

2. 如果缓存里没有我们想要的数据,我们会先去查询数据库,然后将数据库查出来的数据写到缓存中。

3. 最后将数据返回给请求

缓存与数据库双写一致问题:如果仅仅只是查询的话,缓存的数据和数据库的数据是没有问题的,但是当更新的时候很可能造成数据库和缓存的数据不一致

从理论上说,只要我们设置了键的过期时间,我们就能保证缓存和数据库的数据最终是一致的。因为只要缓存数据过期了,就会被删除。随后读的时候,因为缓存里没有,就可以查数据库的数据,然后将数据库查出来的数据写入到缓存中。

除了设置过期时间,我们还需要做更多的措施来尽量避免数据库与缓存处于不一致的情况发生。

在进行更新操作的时候,一般有两种选择:1. 先操作数据库,后操作缓存。2. 先操作缓存,后操作数据库。

我们需要明确的是,无论我们选择更新操作的哪一个,都希望两个操作同时成功或失败,所以这就演变为一个==分布式事务问题。==

1. 操作缓存
1. 更新缓存

2. 删除缓存

一般都是采用删除缓存的缓存策略,原因:

1. 在高并发环境下,无论是先操作数据库还是后操作数据库,如果加上更新缓存,那就更容易导致数据库与缓存数据不一致问题。

2. 如果每次更新了数据库,都要更新缓存,倒不如直接删除缓存数据,等再次读取时,缓存里没有,那再去数据库找,然后将数据写入缓存里面。
2. 先更新数据库,再删除缓存
1. 在正常情况下,先操纵数据库,成功

2. 再删除缓存,成功

如果原子性被破坏:

1. 第1步成功(操作数据库),第2步失败(删除缓存),会导致数据库里是新数据,缓存中是旧数据

2. 如果第一步失败,可以直接返回错误(Exception),不会出现数据不一致

删除缓存失败的解决思路:1. 将需要删除的key发送到消息队列中。2. 自己消费消息,获得需要删除的key。3. 不断重试删除操作,直到成功。

3. 先删除缓存,再更新数据库

将缓存数据删除,更新数据库,然后后期读操作的时候,查询缓存里面没有对应的数据则从数据库读取,再将数据写入缓存。

4. 并发情况下解决数据库与缓存不一致的思路

将删除缓存、修改数据库、读取缓存等的操作积压到队列里面,实现串行化。

Redis 数据结构?

  • 字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。
  • 如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、Pub/Sub。
  • 如果你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。

Redis分布式锁?

概念:分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰来保证一致性。

条件:

互斥性:在任意一个时刻,只有一个客户端持有锁。

无死锁:即便持有锁的客户端崩溃或者其他意外事件,锁仍然可以被获取。

容错:只要大部分Redis节点都活着,客户端就可以获取和释放锁

分布式锁的实现:

数据库

Memcached(add命令)

Redis(setnx命令)

Zookeeper(临时节点)
...

先拿命令setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放

如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?

  • 按照逻辑来说这个锁就永远得不到释放了
  • 我记得set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的

假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?

使用keys指令可以扫出指定模式的key列表。

如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

  • 这个时候你要回答redis关键的一个特性:==redis是单线程的。== keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,==scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,== 在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

使用过Redis做异步队列么,你是怎么用的?

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

可不可以不用sleep呢?

list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

能不能生产一次消费多次呢?

使用pub/sub主题订阅者模式,可以实现1:N的消息队列。

pub/sub有什么缺点?

在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq,kafka等。

redis如何实现延时队列?

使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

如果有大量的key需要设置同一时间过期,一般需要注意什么?

  • 如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。==一般需要在时间上加一个随机值,使得过期时间分散一些。==

Redis如何做持久化的?

  • bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。

如果突然机器掉电会怎样?

  • 取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

bgsave的原理是什么?

  • fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

Pipeline有什么好处,为什么要用pipeline?

  • 可以将多次IO往返的时间缩减为一次,==前提是pipeline执行的指令之间没有因果相关性。==使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是==pipeline批次指令的数目。==

Redis的同步机制了解么?

  • Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

是否使用过Redis集群,集群的原理是什么?

  • Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
  • Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

使用Redis有哪些好处?

  • (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
  • (2) 支持丰富数据类型,支持string,list,set,sorted set,hash
  • (3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
  • (4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

redis相比memcached有哪些优势?

  • memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
  • redis的速度比memcached快很多
  • redis可以持久化其数据

redis常见性能问题和解决方案:

  • Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件。
  • 写内存快照时,save命令调度rdbSave函数,会阻塞主线程的工作;
  • AOF在重写的时候会占大量的CPU和内存资源。如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。
  • 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  • 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  • 尽量避免在压力很大的主库上增加从库
  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?

redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
redis 提供 6种数据淘汰策略:

voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

由maxmemory-policy 参数设置淘汰策略:

CONFIG SET maxmemory-policy volatile-lru      #淘汰有过时期的最近最好使用数据

Redis过期策略

  • redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。从过期字典中随机 20 个 key,删除这 20 个 key 中已经过期的 key,如果过期的 key 比率超过 1/4,那就重复步骤 1。

  • 同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。

从库的过期策略

  • 从库不会进行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令 来删除过期的 key
  • 因为指令同步是异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在

本文标题:Redis - 学习

文章作者:Bangjin-Hu

发布时间:2019年10月15日 - 09:22:26

最后更新:2020年03月30日 - 08:03:08

原始链接:http://bangjinhu.github.io/undefined/Redis - 面试总结/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Bangjin-Hu wechat
欢迎扫码关注微信公众号,订阅我的微信公众号.
坚持原创技术分享,您的支持是我创作的动力.